h = [plt('slider',p{1}, 10,'Slider 1',@CBsli);
plt('slider',p{2}, 60,'Slider 2',@CBsli);
plt('slider',p{3},800,'Slider 3',@CBsli);
plt('pop',p{4},cho,'disp("pseudo popup")','label','PSEUDO POPUP');
plt('box',p{5});
uicontrol('Style','radio', 'backgr',[.5 1 1], 'units','norm','pos',p{6},...
'String','RadioButton','Callback',@CB);
uicontrol('Style','popup', 'backgr',[.5 1 1], 'units','norm','pos',p{7},...
'String', cho, 'Callback',@CB);
uicontrol('Style','slider', 'backgr',[.5 1 1], 'units','norm','pos',p{8},...
'String','slider', 'Callback',@CBsli);
uicontrol('Style','pushb', 'backgr',[.5 1 1], 'units','norm','pos',p{9},...
'String','button1', 'Callback',@CB);
uicontrol('Style','checkbox','backgr',[.5 1 1], 'units','norm','pos',p{10},...
'String','check001', 'Callback',@CB);
uicontrol('Style','listbox', 'backgr',[.5 1 1], 'units','norm','pos',p{11});
uicontrol('Style','text', 'backgr',[.5 1 1], 'units','norm','pos',p{12})];
The code above (in blue) is straightforward and will be the preferred style for many of you.
Some of you may prefer more compact code (which I find easier to read), so what follows is
the code that I actually included in gui1.m:
h = [plt('slider',p(1:3),{10 60 800},{'Slider 1' 'Slider 2' 'Slider 3'},@CBsli);
plt('pop',p{4},cho,'disp("pseudo popup")','label','PSEUDO POPUP');
plt('box',p{5});
uic('Style', {'radio' 'popup' 'slider' 'pushb' 'checkbox' 'listbox' 'text'},...
'String',{'RadioButton' cho 'slider' 'button1' 'check001' '' '' },...
'Callb', { @CB @CB @CBsli @CB @CB '' '' },...
'backgr',[.5 1 1],'units','norm','Pos',p(6:end))];
Note that the first three lines have been collapsed into one, since plt can create
multiple pseudo objects with a single command. Also, the last 12 lines that create the 7 uicontrols
has been collapsed onto 4 lines by using uic (which is similar to uicontrol except that it
allows multiple controls to be created with a single command). There is nothing wrong with either
style and which one you choose is largely a matter of taste. The workings of
A second example (afiltS.m)
Now that we have covered most of the basic concepts and techniques, its time to explore the true power
of plt by reviewing the design of a real GUI in one of the application areas that Matlab was designed for.
The application I have chosen is the display and analysis of the classical analog filters. Granted this
is not a particularly novel idea as it probably has been done before in Matlab and other languages,
but nonetheless it serves various educational and practical needs and there is always room to apply
our own slant to the project. I'll start with a relatively modest set of goals:
- Display the magnitude frequency response for the five "classical" analog filters (Butterworth,
Bessel, Chebyshev type 1 & 2, and Elliptic). The user should be able to easily select which
of these filters to display, as well as allow all of them (or any subset) to be plotted
at the same time.
- Interactive selection of the filter order and type (lowpass, highpass, bandpass, stopband).
- Interactive selection of the number of decades to plot as well as the frequency resolution.
- Both numerical entry and slider control of cutoff frequencies and pass/stopband ripple.
- Cursors should be provided which allow for the easy readout of the frequency response at any
point as well as delta readouts to verify stop band and pass band ripples. Peak finding
should be provided as well as the ability to annotate the plot with text and markers
to document features of interest.
As is my habit, I start with a sketch to clarify my thoughts. I decide to use an array of four
pseudo sliders along the top to control the continuously adjustable parameters (edge frequencies & ripple)
The four remaining filter and display parameters are grouped to the left of the sliders inside a
box for visual appeal and to allow those four parameters to be moved around as a group when repositioning.
The Trace IDs to the left of that will be named after the classical filter types and be used to select which
filters to display. The plot and the cursor controls and readouts along the bottom edge are the standard ones
created by the plt pseudo object.
By the way, I've called this program afiltS.m
(supplied in the demo folder) where the "S" stands for "Simplified" because it is a simplified version of
afilt.m (also in the demo folder) which has several more advanced features.
But of course, here we want to start simple.
Ok ... it's time to start writing code.
First, we use the arrange function to initialize the positions of the 10 pseudo objects
we will be creating. The next two lines define band and filter types to be plotted and the
fourth line creates the first pseudo object (a plot) and assigns it a screen position of
p{1}, the first position in the cell array created by the arrange function. Actually
this object is a super pseudo object, including an axis, traces, axis labels, grid lines,
and cursor controls (the last two being pseudo objects in their own right):
function afilt()
p = arrange(10);
band = {'low pass' 'high pass' 'band pass' 'stop band'};
ftypes = {'Butter' 'Bessel', 'Cheby1' 'Cheby2' 'Elliptic'};
S.tr = plt(0,zeros(1,5),'TraceID',ftypes,'Options','logX','xy',p{1},...
'Ylim',[-80 5],'LabelX','\omega (radians/sec)','LabelY','dB');
Notice that we used plt's 'xy' parameter to position the plot
within the figure window (although you will soon learn that this parameter can do far more
than that). The data to be plotted for all 5 traces is defined in the plt call (as it must),
but notice that each trace just contains the single point (0,0). When calling plt from the
command line, you almost always include the actual plot data in the argument list, however
in a GUI more often than not the data supplied is just a placeholder. The real data is
loaded later (by the callback in this example). The callback will need to know where to put
the plotting data, so the trace handles are saved in a 1x5 array called
S.tr (the plt return value). In the code below, we will add the
handles of the remaining pseudo objects to the S structure.
Next, we will define a frame (the box pseudo object), followed by four more pseudo objects
(an edit object and three popups) that will be placed inside that frame:
plt('box', p{2}); % create a frame for the 4 filter parameters
S.n = plt('edit', p{3} ,[5 1 25], @cb,'BackGr',[1 1 1]/12,'label','Ord~er:');
S.band = plt('pop', p{4} ,band, @cb,'index',3,'swap');
S.dec = plt('pop', p{5} ,1:5, @cb,'index',3,'label','Decades:','hide');
S.pts = plt('pop', p{6} ,50*2.^(1:5),@cb,'index',2,'label','Points:', 'hide');
Note that when you change any of these controls, the same callback function (cb) is executed.
For more complicated interfaces you may want to have different callbacks for each control,
but this one function suffices here. For the last popup, the choice vector could have been
specified as [100 200 400 800 1600] but I chose to write it
in terms of powers of 2. The [5 1 25] parameter of the pseudo edit object means that its initial
value will be 5 with min/max limits of 1 and 25. The string 'Order' is used as a label for
the edit object, but notice that there is a tilde character (~) inserted into that string.
This is to change the amount of space (width) allocated to the label. You might want to experiment
by moving and/or removing the tilde character to see how that affects the label width.
Notice that the first popup defined (S.band) includes the 'swap'
parameter at the end of the call. The effect of that extra parameter is described in the
S.Rp = plt('slider',p{7} ,[ 2 .01 9],'Passband ripple', @cb);
S.Rs = plt('slider',p{8} ,[ 40 10 110],'Stopband ripple', @cb);
S.Wn = plt('slider',p{9} ,[.1 .01 10],'Cutoff frequency',@cb,5,'%3.2f 6 2');
S.Wm = plt('slider',p{10},[.3 .01 10],'frequency 2', @cb,5,'%3.2f 6 2');
set(gcf,'user',S); cb; % save parameters for cb, and initialize the plot
% end function afiltS
The function cb() % callback function for all objects
S = get(gcf,'user'); N = plt('edit',S.n); % get handle list and filter order
dec = plt('pop',S.dec); pts = 50*2.^plt('pop',S.pts); % get # of decades and # of points
bi = plt('pop',S.band); % get filter band index (low,high,band,stop)
Wn = plt('slider',S.Wn); Wm = plt('slider',S.Wm); % get filter frequency & frequency 2
Rp = plt('slider',S.Rp); Rs = plt('slider',S.Rs); % get passband and stopband ripple
if bi>2 Wn = [Wn Wm]; v = 'visON'; else v = 'visOFF'; end;
X = round(log10(mean(Wn)) - dec/2 + .5); X = logspace(X,X+dec,pts); % X-axis (radians/sec)
plt('slider',S.Wm,v); % hide or show frequency 2
af(bi,N,Wn,Rp,Rs,X,S.tr); % compute frequency response & set trace data
cur(-1,'xlim',X([1 end])); % set Xaxis limits
%end function cb
The first six lines of the callback retrieve the parameters from every control. (We have to do this because cb doesn't know
which control has been modified.) If we have selected low pass or high pass with the popup control (bi = 1 or 2) then we don't
need the second frequency slider, so we turn it off ('visOFF'). If it is a bandpass or stopband filter (bi = 3 or 4) then
we turn the second frequency slider on ('visON'). A lot happens in the next line, a for loop that sequences thru the five filter
types (Butterworth, Bessel, Chebyshev types 1 and 2, and Elliptic), computing the frequency response by calling the
function cfg() % write configuration file
S = get(gcf,'user'); s = plt('slider',S.sli(1),'get','obj');
cf = {plt('edit',S.n); plt('pop',[S.band S.dec S.pts]); plt('slider',S.sli);
get(s(2),'backgr'); get(S.etr,'color'); get(gcf,'position')};
num = get(S.tr(1),'user'); den = get(S.tr(2),'user');
save(S.cfg,'cf','num','den');
Then right before we initialize the plot, we load the configuration file if it exists
and set the GUI elements to agree with the data in the file:
if exist(S.cfg) load(S.cfg);
% load configuration file if it exists -------------------
plt('edit',S.n,'value', cf{1});
plt('pop',[S.band S.dec S.pts],'index',num2cell(cf{2}));
plt('slider',S.sli,'set',num2cell(cf{3})); set(h,'background',cf{4});
set(S.etr,'color',cf{5}); set(gcf,'position',cf{6});
end;
And finally we add this parameter to the